﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Common.Utilities;
using Common.Utilities.TextMatch;

namespace Workflows.Components.Entities.Distances.FieldDistances
{
    public class PersonFullNameComparer:IAttributeDistanceCalcutor
    {
        private const int MaxEditDistance = 1;
        private const int MinSubStringLen = 5;
        private PersonNameComparer _NameComparer;

        public PersonFullNameComparer()
        {
            this._NameComparer = new PersonNameComparer();

            this._ComparerSettings = new Dictionary<string, string>();
            this._ComparerSettings.Add("UnevenPersonNameConnectionString", "");
            this._ComparerSettings.Add("UnevenPersonNameTableName", "");
            this._ComparerSettings.Add("UnevenPersonNameFieldName", "");
            this._NameComparer.ComparerSettings = this._ComparerSettings;
        }

        #region Implementation of IAttributeComparer

        /// <summary>
        /// load dictionary into memory or read config settings from file
        /// </summary>
        public void InitInMemorySettings()
        {
            this._NameComparer.ComparerSettings = this._ComparerSettings;
            this._NameComparer.InitInMemorySettings();
        }

        /// <summary>
        /// return similarity score in [0,1]
        /// 0 indicate no similarity or null match
        /// 1 indicate perfect match
        /// </summary>
        /// <param name="attrValue1"></param>
        /// <param name="attrValue2"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public double Compare(object attrValue1, object attrValue2, ref MatchResult result)
        {
            return (this._NameComparer).Compare(attrValue1, attrValue2, ref result);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity1"></param>
        /// <param name="entity2"></param>
        /// <param name="attrMetaData"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public double Compare(Entity entity1, Entity entity2, AttributeMetaData attrMetaData, ref MatchResult result)
        {
            Debug.Assert(attrMetaData.ArgNameAttrNameBindings != null &&
                attrMetaData.ArgNameAttrNameBindings.Count==2,
                         "Comparable attributes count must be 2: last name and first name");
            Debug.Assert(attrMetaData.ArgNameAttrNameBindings.ContainsKey("LastName"));
            Debug.Assert(attrMetaData.ArgNameAttrNameBindings.ContainsKey("FirstName"));
            
            result = MatchResult.NullMatch;
            string attrName1 = attrMetaData.ArgNameAttrNameBindings["LastName"];
            string attrName2 = attrMetaData.ArgNameAttrNameBindings["FirstName"];
            object ln1 = null;
            object fn1 = null;
            object ln2 = null;
            object fn2 = null;
            if (entity1.ComparableAttributes.ContainsKey(attrName1))
                ln1 = entity1.ComparableAttributes[attrName1];
            if (entity1.ComparableAttributes.ContainsKey(attrName2))
                fn1 = entity1.ComparableAttributes[attrName2];
            if (entity2.ComparableAttributes.ContainsKey(attrName1))
                ln2 = entity2.ComparableAttributes[attrName1];
            if (entity2.ComparableAttributes.ContainsKey(attrName2))
                fn2 = entity2.ComparableAttributes[attrName2];

            if ((ln1 == null && fn1 == null) || (ln2 == null && fn2 == null))
                return 0;

            // last name match
            MatchResult lnResult = MatchResult.NullMatch;
            double lnScore = 0;
            if (ln1 == null || ln2 == null)
                lnScore = 0;
            else
            {
                string lastName1 = ln1.ToString();
                string lastName2 = ln2.ToString();
                lnScore = this.CompareName(lastName1, lastName2, ref lnResult);
            }

            // first name match
            MatchResult fnResult = MatchResult.NullMatch;
            double fnScore = 0;
            if (fn1 == null || fn2 == null)
                fnScore = 0;
            else
            {
                string firstName1 = fn1.ToString();
                string firstName2 = fn2.ToString();
                fnScore = this.CompareName(firstName1, firstName2, ref fnResult);
            }
            double noSwapScore = (lnScore + fnScore)/2;
            if(lnResult==MatchResult.NegativeMatch || fnResult==MatchResult.NegativeMatch)
            {
                noSwapScore = 0;
            }

            // swap 
            double swapScore = 0;
            double swapScore1 = 0;
            MatchResult swapResult1 = MatchResult.NullMatch;
            if (ln1 == null || fn2 == null)
                swapScore1 = 0;
            else
            {
                string lastName1 = ln1.ToString();
                string firstName2 = fn2.ToString();
                swapScore1 = this.CompareName(lastName1, firstName2, ref swapResult1);
            }
            double swapScore2 = 0;
            MatchResult swapResult2 = MatchResult.NullMatch;
            if (fn1 == null || ln2 == null)
                swapScore2 = 0;
            else
            {
                string firstName1 = fn1.ToString();
                string lastName2 = ln2.ToString();
                swapScore2 = this.CompareName(lastName2, firstName1, ref swapResult2);
            }
            if (swapScore1 >= 0.6 && swapScore2 >= 0.6)
                swapScore = (swapScore1 + swapScore2) / 2;
            else
                swapScore = 0;
            
            if(swapResult1==MatchResult.NegativeMatch || swapResult2==MatchResult.NegativeMatch)
            {
                swapScore = 0;
            }

            if (noSwapScore > 0 || swapScore > 0)
            {
                result = MatchResult.PositiveMatch;
                if(noSwapScore==1.0 || swapScore==1.0)
                    result = MatchResult.PerfectMatch;

                //result.MatchedAttributeNames = new List<string>();
                if (noSwapScore >= swapScore)
                {
                    //if (lnScore > 0)
                    //    //result.MatchedAttributeNames.Add(comparableAttributeNames[0]);
                    //if (fnScore > 0)
                        //result.MatchedAttributeNames.Add(comparableAttributeNames[1]);
                    return noSwapScore;
                }
                else
                {
                    //result.MatchedAttributeNames.Add(comparableAttributeNames[0]);
                    //result.MatchedAttributeNames.Add(comparableAttributeNames[1]);
                    return swapScore;
                }
            }
            else
            {
                result = MatchResult.NegativeMatch;
                return 0;
            }
        }

        /// <summary>
        /// argument to bind attribute name
        /// </summary>
        public string[] ArgumentNames
        {
            get
            {
                return new string[] {"LastName", "FirstName"};
            }
        }

        private Dictionary<string, string> _ComparerSettings;
        public Dictionary<string, string> ComparerSettings
        {
            get
            {
                return _ComparerSettings;
            }
            set
            {
                this._ComparerSettings = value;
                this._NameComparer.ComparerSettings = value;
                this._NameComparer.InitInMemorySettings();
            }
        }

        #endregion

        private double CompareName(string strVal1, string strVal2, ref MatchResult result)
        {
            try
            {
                if (strVal1.ToLower() == strVal2.ToLower())
                {
                    result = MatchResult.PerfectMatch;
                    return 1.00;
                }
                else if (strVal1.Length>=1 && strVal2.Length>=1 && 
                    (strVal1.Length == 1 || strVal2.Length == 1))
                {
                    if (strVal1.Substring(0, 1).ToLower() == strVal2.Substring(0, 1).ToLower())
                    {
                        result = MatchResult.PositiveMatch;
                        return 0.55;
                    }
                }
                else if (StringUtil.EditDistance(strVal1, strVal2, false) <= MaxEditDistance &&
                    StringUtil.LongestCommonSubsequence(strVal1, strVal2) >= MinSubStringLen)
                {
                    int commonStrLen = StringUtil.LongestCommonSubsequence(strVal1, strVal2);
                    result = MatchResult.PositiveMatch;
                    return (double) commonStrLen/Math.Max(strVal1.Length, strVal2.Length);
                }
                //else if (this.GetSoundexEncoding(strVal1) == this.GetSoundexEncoding(strVal2))
                //{
                //    result = MatchResult.PositiveMatch;
                //    return 0.50;
                //}
                //else if (this.GetMetaphoneEncoding(strVal1) == this.GetMetaphoneEncoding(strVal2))
                //{
                //    result = MatchResult.PositiveMatch;
                //    return 0.35;
                //}

                result = MatchResult.NegativeMatch;
                return 0;
            }
            catch (Exception ex)
            {
                ExceptionHandler.HandlesException(ex);
                return 0.0;
            }
        }

        #region phonetic
        /// <summary>
        /// soundex encoding of string
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public string GetSoundexEncoding(string value)
        {
            SoundexConverter converter = new SoundexConverter();
            return converter.Convert(value);
        }

        /// <summary>
        /// metaphone encoding
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public string GetMetaphoneEncoding(string value)
        {
            MetaphoneConverter converter = new MetaphoneConverter();
            return converter.Convert(value);
        }
        #endregion
    }
}
